package cluster

import (
	"context"
	"errors"
	"flare-admin/db/dao"
	"flare-admin/db/sqlc/cluster"
	"flare-admin/ecode"
	grpcclient "flare-admin/grpc/client"
	"flare-admin/http/model"
	"flare-admin/service/cluster/resource"
	"flare-admin/service/cluster/schedule"
	"flare-admin/service/external/k8s/clientset"

	"github.com/gin-gonic/gin"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgconn"
	"github.com/sirupsen/logrus"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/yaml"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	metrics "k8s.io/metrics/pkg/client/clientset/versioned"
)

// Service 处理所有和集群、集群资源相关的操作
type Service struct {
	dao        *dao.DBTXQuery
	Pod        *resource.PodService
	Deployment *resource.DeploymentService
	Schedule   *schedule.Service
}

func New(dao *dao.DBTXQuery) *Service {
	return &Service{
		dao:        dao,
		Pod:        resource.NewPodService(dao),
		Deployment: resource.NewDeploymentService(dao),
		Schedule:   schedule.New(dao),
		// Deployment: &resource.DeploymentService{},
	}
}

// getNodesByClusterName 获取指定K8s集群中的所有Node信息
func (s *Service) getNodesByClusterName(clusterType, clusterName string) ([]model.NodeDescModel, error) {
	// clusterName 用于获取对应的clientset
	cs, err := s.getClientset(clusterType, clusterName)
	if err != nil {
		logrus.Errorf("[getNodesByClusterName] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterName, clusterType, err)
		return nil, err
	}

	nodeList, err := cs.CoreV1().Nodes().List(context.Background(), v1.ListOptions{})
	if err != nil {
		return nil, err
	}
	res := make([]model.NodeDescModel, 0, len(nodeList.Items))
	for _, nodeItem := range nodeList.Items {
		statusType := nodeItem.Status.Conditions[len(nodeItem.Status.Conditions)-1].Type
		status := "Unknown"
		if statusType == corev1.NodeReady {
			status = "Ready"
		} else {
			status = "NotReady"
		}
		role := "worker"
		if _, ok := nodeItem.Labels["node-role.kubernetes.io/control-plane"]; ok {
			role = "control-plane"
		}

		res = append(res, model.NodeDescModel{
			Name:    nodeItem.Name,
			Status:  status,
			Role:    role,
			IP:      nodeItem.Status.Addresses[0].Address,
			Version: nodeItem.Status.NodeInfo.KubeletVersion,
		})
	}
	return res, nil
}

// getNamespacesByClusterName 获取指定K8s集群中的所有Namespace信息
func (s *Service) getNamespacesByClusterName(clusterType, clusterName string) ([]model.NameSpaceInfo, error) {
	cs, err := s.getClientset(clusterType, clusterName)
	if err != nil {
		logrus.Errorf("[getNamespacesByClusterName] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterName, clusterType, err)
		return nil, err
	}
	namespaceList, err := cs.CoreV1().Namespaces().List(context.Background(), v1.ListOptions{})
	if err != nil {
		logrus.Errorf("[GetClusterNamespacesByID] - [ListNamespaces] - failed to list namespaces for cluster %s: %v", clusterName, err)
		return nil, err
	}
	res := make([]model.NameSpaceInfo, 0, len(namespaceList.Items))
	for _, ns := range namespaceList.Items {
		res = append(res, model.NameSpaceInfo{
			Name:   ns.Name,
			Status: string(ns.Status.Phase),
		})
	}
	return res, nil
}

func (s *Service) loadCluster(clusterName, clusterType string) error {
	res, err := s.dao.Cluster.SelectSingleClusterInfo(context.Background(), cluster.SelectSingleClusterInfoParams{
		Name:        clusterName,
		Clustertype: clusterType,
	})
	if err != nil {
		return err
	}
	_, err = clientset.AddClientsetFromBytes(clusterType, clusterName, []byte(res.Kubeconfig))
	return err
}

func (s *Service) GetAllClusterByType(ctx *gin.Context, clusterType string) ([]model.ClusterInfo, error) {
	clusters, err := s.dao.Cluster.SelectAllClusterInfo(ctx, clusterType)
	if err != nil {
		logrus.Errorf("[GetAllClusterByType] - [SelectAllClusterInfo] - failed to select all cluster info for type %s: %v", clusterType, err)
		return nil, err
	}
	res := make([]model.ClusterInfo, 0, len(clusters))
	for _, each := range clusters {
		nodes, err := s.getNodesByClusterName(clusterType, each.Name)
		if err != nil {
			logrus.Errorf("[GetAllClusterByType] - [GetNodesByClusterName] - failed to get nodes for cluster %s: %v", each.Name, err)
			continue
		}
		namespaces, err := s.getNamespacesByClusterName(clusterType, each.Name)
		if err != nil {
			logrus.Errorf("[GetAllClusterByType] - [GetNamespacesByClusterName] - failed to get namespaces for cluster %s: %v", each.Name, err)
			continue
		}
		res = append(res, model.ClusterInfo{
			ID:          int(each.ID),
			Name:        each.Name,
			Type:        each.ClusterType,
			Status:      each.Status,
			KubeVersion: each.KubeVersion,
			Nodes:       nodes,
			NameSpaces:  namespaces,
		})

	}
	return res, nil
}

func (s *Service) AddClusterConfig(ctx *gin.Context, name, kubeconfig, clusterType string) (model.AddClusterResponse, error) {
	// 1. 通过kubeconfig内容直接创建clientset  2. 使用clientset校验连接性，并获取集群的版本信息
	versionInfo, err := clientset.AddClientsetFromBytes(clusterType, name, []byte(kubeconfig))
	if err != nil {
		logrus.Errorf("[AddClusterConfig] - [AddClientsetFromBytes] - failed to add clientset for cluster %s: %v", name, err)
		return model.AddClusterResponse{
			Message: "无法添加",
		}, err
	}

	err = s.dao.Cluster.AddClusterConfig(ctx, cluster.AddClusterConfigParams{
		Clustertype: clusterType,
		Status:      "Ready",
		Name:        name,
		Kubeconfig:  kubeconfig,
		Kubeversion: versionInfo,
	})
	if err != nil {
		logrus.Errorf("[AddClusterConfig] - [AddClusterConfig] - failed to add cluster config for %s: %v", name, err)
		// 回滚map中的clientset
		_ = clientset.DeleteMapKey(clientset.BuildMapKey(clusterType, name))
		var pgErr *pgconn.PgError
		if errors.As(err, &pgErr) && pgErr.Code == ecode.ErrPgUniqueKey.String() {
			err = ecode.ErrClusterNameExists
		}
		return model.AddClusterResponse{
			Message: "添加错误",
		}, err
	}
	return model.AddClusterResponse{Message: model.SuccessMessage}, nil
}

func (s *Service) DeleteClusterConfig(ctx *gin.Context, clusterType, name string) (model.DeleteClusterResponse, error) {
	// TODO: 实现删除逻辑
	return model.DeleteClusterResponse{
		Message: "未实现",
	}, nil
}

func (s *Service) GetClusterByID(ctx *gin.Context, clusterID int) (model.ClusterMetaInfo, error) {
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[GetClusterByID] - [SelectSingleClusterConfigByID] - failed to select cluster by ID %d: %v", clusterID, err)
		if errors.Is(err, pgx.ErrNoRows) {
			return model.ClusterMetaInfo{}, ecode.ErrClusterNotExists
		}
		return model.ClusterMetaInfo{}, err
	}
	res := model.ClusterMetaInfo{}
	res.ClusterModel.ID = int(clusterInfo.ID)
	res.ClusterModel.Name = clusterInfo.Name
	res.ClusterModel.Status = clusterInfo.Status
	res.ClusterModel.Type = clusterInfo.ClusterType
	res.ClusterModel.KubeVersion = clusterInfo.KubeVersion
	// 获取该集群的node列表
	nodes, err := s.getNodesByClusterName(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[GetClusterByID] - 获取节点node列表失败: %v", err)
		return model.ClusterMetaInfo{}, err
	}
	res.ClusterModel.Nodes = nodes

	// TODO: 获取该集群的命名空间
	cs, err := s.getClientset(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[GetClusterByID] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterInfo.Name, clusterInfo.ClusterType, err)
		return model.ClusterMetaInfo{}, err
	}
	list, err := cs.CoreV1().Namespaces().List(ctx, v1.ListOptions{})
	if err != nil {
		logrus.Errorf("[GetClusterByID] - [ListNamespaces] - failed to list namespaces for cluster %s: %v", clusterInfo.Name, err)
		return model.ClusterMetaInfo{}, err
	}
	for _, ns := range list.Items {
		res.NameSpaces = append(res.NameSpaces, model.NameSpaceInfo{
			Name: ns.Name,
		})
	}

	return res, nil
}

func (s *Service) GetClusterResourceByID(ctx *gin.Context, clusterID int) (model.ClusterResourceRatioModel, error) {
	// 1. 获取集群信息
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [SelectSingleClusterConfigByID] - failed to select cluster config by ID %d: %v", clusterID, err)
		return model.ClusterResourceRatioModel{}, err
	}

	// 2. 获取metric-clientset
	cs, err := s.getClientset(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterInfo.Name, clusterInfo.ClusterType, err)
		return model.ClusterResourceRatioModel{}, ecode.ErrClusterNotExists
	}
	metricsCs, err := s.buildMetricClientset([]byte(clusterInfo.Kubeconfig))
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [BuildMetricClientset] - failed to build metrics clientset for cluster %s: %v", clusterInfo.Name, err)
		return model.ClusterResourceRatioModel{}, ecode.ErrClusterResourceFetching
	}

	// 3. 获取集群资源信息
	resourceRatioMap := make(map[string]model.ResourceRatio)
	var (
		totalCPU, allocatableCPU       int64
		totalMemory, allocatableMemory int64
		totalPods, allocatablePods     int
	)

	metricsNodeList, err := metricsCs.MetricsV1beta1().NodeMetricses().List(context.TODO(), v1.ListOptions{})
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [ListNodes] - failed to list nodes for cluster %s: %v", clusterInfo.Name, err)
		return model.ClusterResourceRatioModel{}, ecode.ErrClusterResourceFetching
	}
	for _, node := range metricsNodeList.Items {
		// 3.1 获取CPU容量信息
		allocatableCPU += node.Usage.Cpu().MilliValue()
		// 3.2 获取内存容量信息
		allocatableMemory += node.Usage.Memory().Value()
		// 3.3 获取Pod容量信息（很不幸，这里拿不到）
		//logrus.Infof("[GetClusterResourceByID] - [ListNodes] - node %s allocatable pods: %v", node.Name, node.Usage.Pods())
		//allocatablePods += int(node.Usage.Pods().Size())
	}

	// Pod的指标从这里拿吧
	podsList, err := metricsCs.MetricsV1beta1().PodMetricses("").List(context.TODO(), v1.ListOptions{})
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [ListPods] - failed to list pods for cluster %s: %v", clusterInfo.Name, err)
		return model.ClusterResourceRatioModel{}, ecode.ErrClusterResourceFetching
	}
	allocatablePods += len(podsList.Items)

	nodeList, err := cs.CoreV1().Nodes().List(context.TODO(), v1.ListOptions{})
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [ListNodes] - failed to list nodes for cluster %s: %v", clusterInfo.Name, err)
		return model.ClusterResourceRatioModel{}, ecode.ErrClusterResourceFetching
	}
	for _, node := range nodeList.Items {
		// 3.4 获取CPU总量信息
		if cpuQuantity, ok := node.Status.Capacity[corev1.ResourceCPU]; ok {
			totalCPU += cpuQuantity.MilliValue()
		}
		// 3.5 获取内存总量信息
		if memoryQuantity, ok := node.Status.Capacity[corev1.ResourceMemory]; ok {
			totalMemory += memoryQuantity.Value()
		}
		// 3.6 获取Pod总量信息
		if podQuantity, ok := node.Status.Capacity[corev1.ResourcePods]; ok {
			totalPods += int(podQuantity.Value())
		}
	}

	// 3.7 获取GPU信息
	gpuc := grpcclient.GetGpuMetricClient()
	gpuMetrics, err := gpuc.GetOverAllGPUMetrics(ctx, nil)
	if err != nil {
		logrus.Errorf("[GetClusterResourceByID] - [GetOverAllGPUMetrics] - failed to get GPU metrics for cluster %s: %v", clusterInfo.Name, err)
	} else {
		resourceRatioMap["gpu"] = model.ResourceRatio{
			Used:  gpuMetrics.Used,
			Total: gpuMetrics.Used + gpuMetrics.Free,
			Unit:  "MB",
		}
	}

	resourceRatioMap["cpu"] = model.ResourceRatio{
		Used:  float64(allocatableCPU) / 1000,
		Total: float64(totalCPU) / 1000,
		Unit:  "Cores",
	}
	resourceRatioMap["memory"] = model.ResourceRatio{
		Used:  float64(allocatableMemory) / (1024 * 1024 * 1024),
		Total: float64(totalMemory) / (1024 * 1024 * 1024),
		Unit:  "GiB",
	}
	resourceRatioMap["pods"] = model.ResourceRatio{
		Used:  float64(allocatablePods),
		Total: float64(totalPods),
		Unit:  "Pods",
	}
	return model.ClusterResourceRatioModel{ResourceRatioMap: resourceRatioMap}, nil
}

// GetClusterNodesByID 获取指定集群ID的所有Node信息
func (s *Service) GetClusterNodesByID(ctx *gin.Context, clusterID int) ([]model.NodeDescModel, error) {
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[GetClusterNodesByID] - [SelectSingleClusterConfigByID] - failed to select cluster config by ID %d: %v", clusterID, err)
		return nil, err
	}
	nodes, err := s.getNodesByClusterName(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[GetClusterNodesByID] - [GetNodesByClusterName] - failed to get nodes for cluster %s: %v", clusterInfo.Name, err)
		return nil, err
	}
	return nodes, nil
}

// GetClusterNamespacesByID 获取指定集群ID的所有命名空间信息
func (s *Service) GetClusterNamespacesByID(ctx *gin.Context, clusterID int) ([]model.NameSpaceInfo, error) {
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[GetClusterNamespacesByID] - [SelectSingleClusterConfigByID] - failed to select cluster config by ID %d: %v", clusterID, err)
		return nil, err
	}
	namespaces, err := s.getNamespacesByClusterName(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[GetClusterNamespacesByID] - [GetNamespacesByClusterName] - failed to get namespaces for cluster %s: %v", clusterInfo.Name, err)
		return nil, err
	}
	return namespaces, nil
}

func (s *Service) DeletePodInClusterID(ctx *gin.Context, clusterId int, namespace string, podName string) (string, error) {
	// 1. 获取集群信息
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterId))
	if err != nil {
		logrus.Errorf("[DeletePodInClusterID] - failed to select cluster config by ID %d: %v", clusterId, err)
		return "", err
	}

	// 2. 获取clientset
	cs, err := s.getClientset(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[DeletePodInClusterID] - failed to get clientset for cluster %s of type %s: %v", clusterInfo.Name, clusterInfo.ClusterType, err)
		return "", ecode.ErrClusterNotExists
	}

	// 3. 删除Pod
	err = cs.CoreV1().Pods(namespace).Delete(ctx, podName, v1.DeleteOptions{})
	if err != nil {
		logrus.Errorf("[DeletePodInClusterID] - [Delete] - failed to delete pod %s in namespace %s of cluster %s: %v", podName, namespace, clusterInfo.Name, err)
		return "", ecode.ErrDeleteResource
	}
	return model.SuccessMessage, nil
}

func (s *Service) DeleteDeploymentInClusterID(ctx *gin.Context, clusterID int, namespace string, deploymentName string) (string, error) {
	// 1. 获取集群信息
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[DeleteDeploymentInClusterID] - [SelectSingleClusterConfigByID] - failed to select cluster config by ID %d: %v", clusterID, err)
		return "", err
	}

	// 2. 获取clientset
	cs, err := s.getClientset(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[DeleteDeploymentInClusterID] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterInfo.Name, clusterInfo.ClusterType, err)
		return "", ecode.ErrClusterNotExists
	}

	// 3. 删除Deployment
	err = cs.AppsV1().Deployments(namespace).Delete(ctx, deploymentName, v1.DeleteOptions{})
	if err != nil {
		logrus.Errorf("[DeleteDeploymentInClusterID] - [Delete] - failed to delete deployment %s in namespace %s of cluster %s: %v", deploymentName, namespace, clusterInfo.Name, err)
		return "", ecode.ErrDeleteResource
	}
	return model.SuccessMessage, nil
}

func (s *Service) CreateDeploymentByYaml(ctx *gin.Context, clusterID int, yamlContent string) (string, error) {
	// 1. 获取集群信息
	clusterInfo, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, int32(clusterID))
	if err != nil {
		logrus.Errorf("[CreateDeploymentByYaml] - [SelectSingleClusterConfigByID] - failed to select cluster config by ID %d: %v", clusterID, err)
		return "", err
	}
	// 2. 获取clientset
	cs, err := s.getClientset(clusterInfo.ClusterType, clusterInfo.Name)
	if err != nil {
		logrus.Errorf("[CreateDeploymentByYaml] - [GetClientset] - failed to get clientset for cluster %s of type %s: %v", clusterInfo.Name, clusterInfo.ClusterType, err)
		return "", ecode.ErrClusterNotExists
	}
	// 3. 创建Deployment
	// 3.1 反序列化yaml为deployment
	deployment := appsv1.Deployment{}
	if err = yaml.Unmarshal([]byte(yamlContent), &deployment); err != nil {
		logrus.Errorf("[CreateDeploymentByYaml] - [Unmarshal] - failed to unmarshal YAML content: %v", err)
		return "", ecode.ErrCreateResource
	}
	// logrus.Infof("[CreateDeploymentByYaml] - [Unmarshal] - deployment: %v", deployment)

	_, err = cs.AppsV1().Deployments(deployment.Namespace).Create(ctx, &deployment, v1.CreateOptions{})
	if err != nil {
		logrus.Errorf("[CreateDeploymentByYaml] - [CreateDeploymentFromYaml] - failed to create deployment from YAML in cluster %s: %v", clusterInfo.Name, err)
		return "", ecode.ErrCreateResource
	}
	return model.SuccessMessage, nil
}

/*
getCLientset 通过clusterType和clusterName获取对应的kubernetes.Clientset
获取过程是懒加载的，首先尝试从内存中获取，若不存在，则从数据库中存储的kubeconfig中的获取，并存于map中

注：该方法是线程安全的
*/
func (s *Service) getClientset(clusterType, clusterName string) (*kubernetes.Clientset, error) {
	key := clientset.BuildMapKey(clusterType, clusterName)
	cs, err := clientset.GetClientset(key)
	if errors.Is(err, ecode.ErrClusterNotExists) {
		if err = s.loadCluster(clusterName, clusterType); err != nil {
			return nil, err
		}
	}
	// 再次尝试获取
	cs, err = clientset.GetClientset(key)
	if err != nil {
		return nil, err
	}
	return cs, nil
}

func (s *Service) buildMetricClientset(kubeconfig []byte) (*metrics.Clientset, error) {
	cfg, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig)
	if err != nil {
		logrus.Errorf("[buildMetricClientset] - [BuildConfigFromBytes] - failed to build config from kubeconfig bytes: %v", err)
		return nil, err
	}
	// 创建metrics客户端
	metricsClient, err := metrics.NewForConfig(cfg)
	if err != nil {
		logrus.Errorf("[buildMetricClientset] - [NewForConfig] - failed to create metrics client: %v", err)
		return nil, err
	}
	return metricsClient, nil
}

func (s *Service) LoadClusterConfigFromDB(ctx *gin.Context, clusterID int32) (string, error) {
	info, err := s.dao.Cluster.SelectSingleClusterConfigByID(ctx, clusterID)
	if err != nil {
		logrus.Errorf("[LoadClusterConfigFromDB] - [GetClusterByID] - failed to get cluster by ID %d: %v", clusterID, err)
		return "", err
	}
	return clientset.AddClientsetFromBytes(info.ClusterType, info.Name, []byte(info.Kubeconfig))
}
